options(future.globals.maxSize = 2 * 1024 * 10E6);
.cran_libs <- c("purrr", "jsonlite", "httr", "summarytools", "munsell", "cachem"
, "SmartEDA", "htmltools", "slider", "stringi", "magrittr", "plotly"
, "DT", "data.table", "pdftools", "lubridate", "future", "furrr", "future.callr")
.git_libs <- paste0("book.of.", c("utilities", "features", "workflow")) |> c("architect")
if (!"book.of.workflow" %in% dir(sprintf("%s/library", R.home()))){
if (!"remotes" %in% dir(sprintf("%s/library", R.home()))){
htmltools::tags$span(style = "text-deoration:italic; color:#333333; ", "... installing missing package 'remotes'")
install.packages("remotes", repos = "https://cloud.r-project.org")
}
htmltools::tags$span(
style = "text-deoration:italic; color:#333333; "
, "... installing missing packages 'book.of.utilities', 'book.of.features', 'book.of.workflow', 'architect'"
);
remotes::install_github("delriaan/book.of.utilities", subdir = "pkg")
remotes::install_github("delriaan/book.of.features", subdir = "pkg")
remotes::install_github("delriaan/book.of.workflow", subdir = "pkg")
remotes::install_github("delriaan/architect", subdir = "pkg")
}
library(book.of.workflow)
load_unloaded(!!!.cran_libs, autoinstall = TRUE)
load_unloaded(!!!.git_libs)
urls <- list(
data = list(
`Data Dictionary` = "https://www.medicaid.gov/medicaid-chip-program-information/by-topics/prescription-drugs/downloads/recordspecficationanddefinitions.pdf"
, Data = "https://download.medicaid.gov/data/drugproducts1q_2023.csv"
) |> imap(\(x, y) tags$li(tags$a(href = x, y))) |> tags$ol()
, git_libs = map(.git_libs, \(x) tags$li(tags$a(title = paste0("delriaan/", x), x))) |> list() |> tags$ul()
, openFDA = "https://download.open.fda.gov/drug/ndc/drug-ndc-0001-of-0001.json.zip"
)
.cache <- cache_disk(dir = "r_session_cache")
Data
Wrangling
Retrieve Data
# MDRP database data
if (rlang::is_empty(dir(params$data, pattern = "api_data[.][Rr]data$"))){
if (!"api_data" %in% ls()){
api_data <- urls$data |>
as.character() |>
stri_extract_all_regex("http.+csv", simplify = TRUE) |>
as.vector() %T>%
(\(x) tags$p(sprintf("Retrieve data from '%s'", x)) |> print())() |>
GET() |>
content() |>
rawToChar() |>
(\(x) read.csv(text = x, ))() |>
as.data.table(na.rm = FALSE) |>
modify_at(c(4:7), as.character) %>%
modify_at(ls(., pattern = "Date"), lubridate::mdy) %>%
setnames(stri_replace_all_fixed(names(.) |> tolower(), ".", "_"))
}
if (!"api_dictionary" %in% ls()){
api_dictionary <- invisible(urls$data |>
as.character() |>
stri_extract_all_regex("http.+pdf", simplify = TRUE) |>
as.vector() |>
GET() |>
content() |>
pdf_text())
.summary_labels <- names(api_data) |>
rlang::set_names() |>
map_chr(\(x) stri_replace_all_fixed(x, ".", " ", vectorize_all = FALSE)) |>
imap_chr(\(x, y){
api_dictionary |>
stri_extract_all_regex(
sprintf(
fmt = "(%s)[:]\n.+"
, stri_replace_all_fixed(
x
, c("Pkg"
, "Intro"
, "COD Status"
, "FDA Application Number"
, "FDA Therapeutic Equivalence Code"
)
, c("Package"
, "Intro."
, "Covered Outpatient Drug [(]COD[)] Status"
, "FDA Application Number/OTC Monograph Number"
, "TEC"
)
, vectorize_all = FALSE
)
)
, simplify = TRUE
) |>
stats::na.omit() |>
as.vector() |>
discard(\(x) x == "") %>%
(\(i){
.out <- ifelse(identical(character(), i), y, paste(i, collapse = "\n"))
ifelse(stri_length(.out) > 50, paste0(.out, " ..."), .out)
})()
});
iwalk(.summary_labels, \(x, y){
.label = x;
api_data <<- modify_at(api_data, y, \(i){ attr(i, "label") <- .label; i })
})
}
} else {
if (!"api_data" %in% ls()){
dir(params$data, pattern = "api_data[.][Rr]data$") |> load()
}
}
Retrieve data from 'https://download.medicaid.gov/data/drugproducts1q_2023.csv'
# openFDA supplementary data
if (rlang::is_empty(dir(params$data, pattern = "openFDA_ndc[.][Rr]data$"))){
if (!"openFDA_ndc" %in% ls()){ openFDA_ndc <- (\(x){
json.file <- "drug-ndc-0001-of-0001.json";
if (!json.file %in% dir()){
GET(urls$openFDA, write_disk(x, TRUE))
unzip(zipfile = x)
}
read_json(path = json.file);
})("ndc_json.zip") |> (\(x){
data.table::rbindlist(modify_at(x$results, 1:length(x$results), as.data.table), fill = TRUE) |>
setattr("metadata", x$meta)
})()
}
} else {
if (!"openFDA_ndc" %in% ls()){
dir(params$data, pattern = "openFDA_ndc") |> load()
}
}
Prepare Data
NDC sequences come in a various formats, usually a
4-4-x, 5-4-x, or 5-3-x sequence
(each integer indicating string length). Sometimes other formats arise,
so normalizing all NDC sequences is a good idea, especially when there
is a desire (or need) to join different data containing intersecting
NDCs.
The following shows the NDC sequences in the OpenFDA and
MDRP data:
I’ll create a function to check NDC formats and conform them in order
to join the two data sets using a conformed NDC sequence:
check_ndc_format <- \(lc, pc){
pc <- modify_if(pc, \(i) stri_length(i) < 3, \(i) stri_pad_left(i, width = 3, pad = "0"))
lc <- modify_if(
lc
, \(i) stri_length(i) < 4
, \(i) stri_pad_left(i, width = ifelse(stri_length(pc) == 3, 5, 4), pad = "0")
)
paste(lc, pc, sep = "-")
}
if (rlang::is_empty(dir(params$data, pattern = "master_drug_data[.][Rr]data$"))){
if (!"master_drug_data" %in% ls()){
master_drug_data <- (\(x, i) x[
i, on = "alt_ndc==product_ndc"
, nomatch = 0
, allow.cartesian = TRUE
, `:=`(pharm_class = pharm_class
, dea_schedule = dea_schedule
, product_type = product_type
, route = route
, marketing_category = marketing_category
)
, by = .EACHI
])(api_data[, alt_ndc := map2_chr(labeler_code, product_code, check_ndc_format)]
, openFDA_ndc)
master_drug_data[, `:=`(
pharm_class := map_chr(pharm_class, \(x) unlist(x) %||% "~")
, route := map_chr(route, \(x) unlist(x) %||% "~")
)]
}
} else {
if (!"master_drug_data" %in% ls()){
dir(params$data, pattern = "master_drug_data[.][Rr]data$", full.names = TRUE) |> load()
}
}
Temporal
Analysis
master_drug_data is a great dataset for constructing
simple, time-based metrics. Given the natural order of the types of
events, it is easy to setup event sequence metrics using package lubridate
if (rlang::is_empty(dir(pattern = "ndc_events[.][Rr]data$"))){
if (!"ndc_events" %in% ls()){
ndc_events <- define(
master_drug_data
, ~alt_ndc + fda_product_name + drug_category +
pharm_class + dea_schedule + product_type +
route + marketing_category + fda_approval_date +
market_date + termination_date + reactivation_date
, unique(.SD)
, days_to_market = duration(as.integer(market_date) - as.integer(fda_approval_date), units = "days")
, on_market_age = duration({
ifelse(is.na(termination_date), ifelse(is.na(reactivation_date), today(), reactivation_date), termination_date) -
ifelse(is.na(reactivation_date), ifelse(is.na(termination_date), market_date, termination_date), reactivation_date)
}, units = "days")
, days_market_absent = duration({
ifelse(is.na(reactivation_date), today(), reactivation_date) -
ifelse(is.na(termination_date), today(), termination_date)
}, units = "days")
);
rlang::set_names(
c("Days between approval \nand market release"
, "Days active on market"
, "Days most-recently \nabsent from market")
, c("days_to_market", "on_market_age", "days_market_absent")
)|>
iwalk(\(x, y) rlang::parse_expr(sprintf("setattr(ndc_events$%s, \"label\", \"%s\")", y, x)) |> eval(envir = .GlobalEnv))
}
} else {
if (!"ndc_events" %in% ls()){
dir(pattern = "ndc_events[.][Rr]data$", full.names = TRUE) |> load()
}
}
LS0tDQp0aXRsZTogIk1lZGljYWlkIERydWcgUmViYXRlIFByb2dyYW0gKE1EUlApIERhdGFiYXNlIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCnBhcmFtczoNCiAgZGF0YTogZGF0YQ0KLS0tDQoNCmBgYHtyIHNldHVwLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0Kb3B0aW9ucyhmdXR1cmUuZ2xvYmFscy5tYXhTaXplID0gMiAqIDEwMjQgKiAxMEU2KTsNCg0KLmNyYW5fbGlicyA8LSBjKCJwdXJyciIsICJqc29ubGl0ZSIsICJodHRyIiwgInN1bW1hcnl0b29scyIsICJtdW5zZWxsIiwgImNhY2hlbSINCiAgICAgICAgICAgICAgICAsICJTbWFydEVEQSIsICJodG1sdG9vbHMiLCAic2xpZGVyIiwgInN0cmluZ2kiLCAibWFncml0dHIiLCAicGxvdGx5Ig0KICAgICAgICAgICAgICAgICwgIkRUIiwgImRhdGEudGFibGUiLCAicGRmdG9vbHMiLCAibHVicmlkYXRlIiwgImZ1dHVyZSIsICJmdXJyciIsICJmdXR1cmUuY2FsbHIiKQ0KLmdpdF9saWJzIDwtIHBhc3RlMCgiYm9vay5vZi4iLCBjKCJ1dGlsaXRpZXMiLCAiZmVhdHVyZXMiLCAid29ya2Zsb3ciKSkgfD4gYygiYXJjaGl0ZWN0IikNCiAgICAgICAgICAgICAgDQppZiAoISJib29rLm9mLndvcmtmbG93IiAlaW4lIGRpcihzcHJpbnRmKCIlcy9saWJyYXJ5IiwgUi5ob21lKCkpKSl7DQogIGlmICghInJlbW90ZXMiICVpbiUgZGlyKHNwcmludGYoIiVzL2xpYnJhcnkiLCBSLmhvbWUoKSkpKXsgDQogICAgaHRtbHRvb2xzOjp0YWdzJHNwYW4oc3R5bGUgPSAidGV4dC1kZW9yYXRpb246aXRhbGljOyBjb2xvcjojMzMzMzMzOyAiLCAiLi4uIGluc3RhbGxpbmcgbWlzc2luZyBwYWNrYWdlICdyZW1vdGVzJyIpDQogICAgaW5zdGFsbC5wYWNrYWdlcygicmVtb3RlcyIsIHJlcG9zID0gImh0dHBzOi8vY2xvdWQuci1wcm9qZWN0Lm9yZyIpIA0KICB9DQogIA0KICBodG1sdG9vbHM6OnRhZ3Mkc3BhbigNCiAgICBzdHlsZSA9ICJ0ZXh0LWRlb3JhdGlvbjppdGFsaWM7IGNvbG9yOiMzMzMzMzM7ICINCiAgICAsICIuLi4gaW5zdGFsbGluZyBtaXNzaW5nIHBhY2thZ2VzICdib29rLm9mLnV0aWxpdGllcycsICdib29rLm9mLmZlYXR1cmVzJywgJ2Jvb2sub2Yud29ya2Zsb3cnLCAnYXJjaGl0ZWN0JyINCiAgICApOw0KICANCiAgcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoImRlbHJpYWFuL2Jvb2sub2YudXRpbGl0aWVzIiwgc3ViZGlyID0gInBrZyIpDQogIHJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCJkZWxyaWFhbi9ib29rLm9mLmZlYXR1cmVzIiwgc3ViZGlyID0gInBrZyIpDQogIHJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCJkZWxyaWFhbi9ib29rLm9mLndvcmtmbG93Iiwgc3ViZGlyID0gInBrZyIpDQogIHJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCJkZWxyaWFhbi9hcmNoaXRlY3QiLCBzdWJkaXIgPSAicGtnIikNCn0NCg0KbGlicmFyeShib29rLm9mLndvcmtmbG93KQ0KbG9hZF91bmxvYWRlZCghISEuY3Jhbl9saWJzLCBhdXRvaW5zdGFsbCA9IFRSVUUpDQpsb2FkX3VubG9hZGVkKCEhIS5naXRfbGlicykNCg0KdXJscyA8LSBsaXN0KA0KICBkYXRhID0gbGlzdCgNCiAgICBgRGF0YSBEaWN0aW9uYXJ5YCA9ICJodHRwczovL3d3dy5tZWRpY2FpZC5nb3YvbWVkaWNhaWQtY2hpcC1wcm9ncmFtLWluZm9ybWF0aW9uL2J5LXRvcGljcy9wcmVzY3JpcHRpb24tZHJ1Z3MvZG93bmxvYWRzL3JlY29yZHNwZWNmaWNhdGlvbmFuZGRlZmluaXRpb25zLnBkZiINCiAgICAsIERhdGEgPSAiaHR0cHM6Ly9kb3dubG9hZC5tZWRpY2FpZC5nb3YvZGF0YS9kcnVncHJvZHVjdHMxcV8yMDIzLmNzdiINCiAgICApIHw+IGltYXAoXCh4LCB5KSB0YWdzJGxpKHRhZ3MkYShocmVmID0geCwgeSkpKSB8PiB0YWdzJG9sKCkNCiAgLCBnaXRfbGlicyA9IG1hcCguZ2l0X2xpYnMsIFwoeCkgdGFncyRsaSh0YWdzJGEodGl0bGUgPSBwYXN0ZTAoImRlbHJpYWFuLyIsIHgpLCB4KSkpIHw+IGxpc3QoKSB8PiB0YWdzJHVsKCkNCiAgLCBvcGVuRkRBID0gImh0dHBzOi8vZG93bmxvYWQub3Blbi5mZGEuZ292L2RydWcvbmRjL2RydWctbmRjLTAwMDEtb2YtMDAwMS5qc29uLnppcCINCiAgKQ0KDQouY2FjaGUgPC0gY2FjaGVfZGlzayhkaXIgPSAicl9zZXNzaW9uX2NhY2hlIikNCmBgYA0KDQojICB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30NCg0KIyMgUHVycG9zZSAmPGJyPiBNZXRhZGF0YSANCg0KVGhpcyBwcm9qZWN0IGV4cGxvcmVzIHRoZSBNZWRpY2FpZCBEcnVnIFJlYmF0ZSBQcm9ncmFtIChNRFJQKSBkYXRhYmFzZSB2aWEgW0FQSV0oaHR0cHM6Ly9kYXRhLm1lZGljYWlkLmdvdi9kYXRhc2V0LzBhZDY1ZmU1LTNhZDMtNWQ3OS1hM2Y5LTc4OTNkZWQ3OTYzYSkgY2FsbHMgKGRhdGFzZXQgZGVzY3JpcHRpb24gW2hlcmVdKGh0dHBzOi8vd3d3Lm1lZGljYWlkLmdvdi9tZWRpY2FpZC9wcmVzY3JpcHRpb24tZHJ1Z3MvbWVkaWNhaWQtZHJ1Zy1yZWJhdGUtcHJvZ3JhbS9tZWRpY2FpZC1kcnVnLXJlYmF0ZS1wcm9ncmFtLWRhdGEvaW5kZXguaHRtbCkpOg0KDQpgciB1cmxzJGRhdGFgDQoNCiMjIyBSZXF1aXJlZCBMSWJyYXJpZXMNCg0KYGBge3IsIGVjaG89RkFMU0UsIGNhY2hlPVRSVUUsIGNhY2hlLmxhenk9VFJVRX0NCnRhZ3MkdGFibGUoDQogIHN0eWxlID0gIndpZHRoOjQ3NXB4OyAiDQogICwgdGFncyR0cigNCiAgICB0YWdzJHRoKHN0eWxlID0gInRleHQtYWxpZ246bWlkZGxlOyBib3JkZXI6IHNvbGlkIDJweCAjQUFBQUZGOyBiYWNrZ3JvdW5kLWNvbG9yOiNFRUVFRUU7ICIsICJDUkFOIiwgY29sc3BhbiA9IDIsIHdpZHRoPSI2NSUiKQ0KICAgICwgdGFncyR0aChzdHlsZSA9ICJ0ZXh0LWFsaWduOm1pZGRsZTsgYm9yZGVyOiBzb2xpZCAycHggI0FBQUFGRjsgYmFja2dyb3VuZC1jb2xvcjojQUFBQUFBOyAiLCB3aWR0aCA9ICIqIiwgIkdpdEh1YiIpDQogICAgKQ0KICAsIHRhZ3MkdHIoDQogICAgICBzdHlsZSA9ICJhbGlnbjp0b3AiDQogICAgICAsIHRhZ3MkdGQoc3R5bGUgPSAidGV4dC1hbGlnbjptaWRkbGU7IGJhY2tncm91bmQtY29sb3I6I0FBQUFBQTsgIg0KICAgICAgICAgICAgICAgICwgbWFwKC5jcmFuX2xpYnNbMTo1XSwgXCh4KSB0YWdzJGxpKHgpKSB8PiBsaXN0KCkgfD4gdGFncyR1bCgpKQ0KICAgICAgLCB0YWdzJHRkKHN0eWxlID0gInRleHQtYWxpZ246bWlkZGxlOyBiYWNrZ3JvdW5kLWNvbG9yOiNBQUFBQUE7ICINCiAgICAgICAgICAgICAgICAsIG1hcCguY3Jhbl9saWJzWzY6bGVuZ3RoKC5jcmFuX2xpYnMpXSwgXCh4KSB0YWdzJGxpKHgpKSB8PiBsaXN0KCkgfD4gdGFncyR1bCgpKQ0KICAgICAgLCB0YWdzJHRkKHN0eWxlID0gImJhY2tncm91bmQtY29sb3I6I0VFRUVFRTsgcGFkZGluZy1yaWdodDoyMHB4OyAiLCB1cmxzJGdpdF9saWJzKQ0KICAgICAgKQ0KICApIHw+IHRhZ3MkcCgpDQpgYGANCg0KIyMgRGF0YSA8YnI+V3JhbmdsaW5nIA0KDQojIyMgUmV0cmlldmUgRGF0YQ0KDQpgYGB7ciBSRVRSSUVWRV9EQVRBLCBjYWNoZT1UUlVFLGNhY2hlLmxhenk9VFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCiMgTURSUCBkYXRhYmFzZSBkYXRhDQppZiAocmxhbmc6OmlzX2VtcHR5KGRpcihwYXJhbXMkZGF0YSwgcGF0dGVybiA9ICJhcGlfZGF0YVsuXVtScl1kYXRhJCIpKSl7DQogIGlmICghImFwaV9kYXRhIiAlaW4lIGxzKCkpeyANCiAgICBhcGlfZGF0YSA8LSB1cmxzJGRhdGEgfD4gDQogICAgICBhcy5jaGFyYWN0ZXIoKSB8PiANCiAgICAgIHN0cmlfZXh0cmFjdF9hbGxfcmVnZXgoImh0dHAuK2NzdiIsIHNpbXBsaWZ5ID0gVFJVRSkgfD4gDQogICAgICBhcy52ZWN0b3IoKSAlVD4lIA0KICAgICAgKFwoeCkgdGFncyRwKHNwcmludGYoIlJldHJpZXZlIGRhdGEgZnJvbSAnJXMnIiwgeCkpIHw+IHByaW50KCkpKCkgfD4NCiAgICAgIEdFVCgpIHw+DQogICAgICBjb250ZW50KCkgfD4NCiAgICAgIHJhd1RvQ2hhcigpIHw+DQogICAgICAoXCh4KSByZWFkLmNzdih0ZXh0ID0geCwgKSkoKSB8Pg0KICAgICAgYXMuZGF0YS50YWJsZShuYS5ybSA9IEZBTFNFKSB8PiANCiAgICAgIG1vZGlmeV9hdChjKDQ6NyksIGFzLmNoYXJhY3RlcikgJT4lIA0KICAgICAgbW9kaWZ5X2F0KGxzKC4sIHBhdHRlcm4gPSAiRGF0ZSIpLCBsdWJyaWRhdGU6Om1keSkgJT4lIA0KICAgICAgc2V0bmFtZXMoc3RyaV9yZXBsYWNlX2FsbF9maXhlZChuYW1lcyguKSB8PiB0b2xvd2VyKCksICIuIiwgIl8iKSkNCiAgfQ0KICANCiAgaWYgKCEiYXBpX2RpY3Rpb25hcnkiICVpbiUgbHMoKSl7IA0KICAgIGFwaV9kaWN0aW9uYXJ5IDwtIGludmlzaWJsZSh1cmxzJGRhdGEgfD4gDQogICAgICBhcy5jaGFyYWN0ZXIoKSB8PiANCiAgICAgIHN0cmlfZXh0cmFjdF9hbGxfcmVnZXgoImh0dHAuK3BkZiIsIHNpbXBsaWZ5ID0gVFJVRSkgfD4gDQogICAgICBhcy52ZWN0b3IoKSB8Pg0KICAgICAgR0VUKCkgfD4NCiAgICAgIGNvbnRlbnQoKSB8PiANCiAgICAgIHBkZl90ZXh0KCkpDQogICAgDQogICAgLnN1bW1hcnlfbGFiZWxzIDwtIG5hbWVzKGFwaV9kYXRhKSB8PiANCiAgICAgICAgcmxhbmc6OnNldF9uYW1lcygpIHw+DQogICAgICAgIG1hcF9jaHIoXCh4KSBzdHJpX3JlcGxhY2VfYWxsX2ZpeGVkKHgsICIuIiwgIiAiLCB2ZWN0b3JpemVfYWxsID0gRkFMU0UpKSB8Pg0KICAgICAgICBpbWFwX2NocihcKHgsIHkpeyANCiAgICAgICAgICBhcGlfZGljdGlvbmFyeSB8PiANCiAgICAgICAgICAgIHN0cmlfZXh0cmFjdF9hbGxfcmVnZXgoDQogICAgICAgICAgICAgIHNwcmludGYoDQogICAgICAgICAgICAgICAgZm10ID0gIiglcylbOl1cbi4rIg0KICAgICAgICAgICAgICAgICwgc3RyaV9yZXBsYWNlX2FsbF9maXhlZCgNCiAgICAgICAgICAgICAgICAgICAgeA0KICAgICAgICAgICAgICAgICAgICAsIGMoIlBrZyINCiAgICAgICAgICAgICAgICAgICAgICAgICwgIkludHJvIg0KICAgICAgICAgICAgICAgICAgICAgICAgLCAiQ09EIFN0YXR1cyINCiAgICAgICAgICAgICAgICAgICAgICAgICwgIkZEQSBBcHBsaWNhdGlvbiBOdW1iZXIiDQogICAgICAgICAgICAgICAgICAgICAgICAsICJGREEgVGhlcmFwZXV0aWMgRXF1aXZhbGVuY2UgQ29kZSINCiAgICAgICAgICAgICAgICAgICAgICAgICkNCiAgICAgICAgICAgICAgICAgICAgLCBjKCJQYWNrYWdlIg0KICAgICAgICAgICAgICAgICAgICAgICAgLCAiSW50cm8uIg0KICAgICAgICAgICAgICAgICAgICAgICAgLCAiQ292ZXJlZCBPdXRwYXRpZW50IERydWcgWyhdQ09EWyldIFN0YXR1cyINCiAgICAgICAgICAgICAgICAgICAgICAgICwgIkZEQSBBcHBsaWNhdGlvbiBOdW1iZXIvT1RDIE1vbm9ncmFwaCBOdW1iZXIiDQogICAgICAgICAgICAgICAgICAgICAgICAsICJURUMiDQogICAgICAgICAgICAgICAgICAgICAgICApDQogICAgICAgICAgICAgICAgICAgICwgdmVjdG9yaXplX2FsbCA9IEZBTFNFDQogICAgICAgICAgICAgICAgICAgICkNCiAgICAgICAgICAgICAgICApDQogICAgICAgICAgICAgICwgc2ltcGxpZnkgPSBUUlVFDQogICAgICAgICAgICAgICkgfD4NCiAgICAgICAgICAgIHN0YXRzOjpuYS5vbWl0KCkgfD4NCiAgICAgICAgICAgIGFzLnZlY3RvcigpIHw+DQogICAgICAgICAgICBkaXNjYXJkKFwoeCkgeCA9PSAiIikgJT4lDQogICAgICAgICAgICAoXChpKXsgDQogICAgICAgICAgICAgIC5vdXQgPC0gaWZlbHNlKGlkZW50aWNhbChjaGFyYWN0ZXIoKSwgaSksIHksIHBhc3RlKGksIGNvbGxhcHNlID0gIlxuIikpDQogICAgICAgICAgICAgIGlmZWxzZShzdHJpX2xlbmd0aCgub3V0KSA+IDUwLCBwYXN0ZTAoLm91dCwgIiAuLi4iKSwgLm91dCkNCiAgICAgICAgICAgIH0pKCkNCiAgICAgICAgfSk7DQogICAgDQogICAgaXdhbGsoLnN1bW1hcnlfbGFiZWxzLCBcKHgsIHkpeyANCiAgICAgIC5sYWJlbCA9IHg7IA0KICAgICAgYXBpX2RhdGEgPDwtIG1vZGlmeV9hdChhcGlfZGF0YSwgeSwgXChpKXsgYXR0cihpLCAibGFiZWwiKSA8LSAubGFiZWw7IGkgfSkgDQogICAgfSkNCiAgfQ0KfSBlbHNlIHsgDQogIGlmICghImFwaV9kYXRhIiAlaW4lIGxzKCkpeyANCiAgICBkaXIocGFyYW1zJGRhdGEsIHBhdHRlcm4gPSAiYXBpX2RhdGFbLl1bUnJdZGF0YSQiLCBmdWxsLm5hbWVzID0gVFJVRSkgfD4gbG9hZCgpIA0KICB9DQp9DQoNCiMgb3BlbkZEQSBzdXBwbGVtZW50YXJ5IGRhdGENCmlmIChybGFuZzo6aXNfZW1wdHkoZGlyKHBhcmFtcyRkYXRhLCBwYXR0ZXJuID0gIm9wZW5GREFfbmRjWy5dW1JyXWRhdGEkIikpKXsNCiAgaWYgKCEib3BlbkZEQV9uZGMiICVpbiUgbHMoKSl7IG9wZW5GREFfbmRjIDwtIChcKHgpeyANCiAgICAgIGpzb24uZmlsZSA8LSAiZHJ1Zy1uZGMtMDAwMS1vZi0wMDAxLmpzb24iOw0KICAgIA0KICAgICAgaWYgKCFqc29uLmZpbGUgJWluJSBkaXIoKSl7IA0KICAgICAgICB0YWdzJHAoc3ByaW50ZigiUmV0cmlldmUgZGF0YSBmcm9tICclcyciLCB1cmxzJG9wZW5GREEpKSB8PiBwcmludCgpDQogICAgICAgIA0KICAgICAgICBHRVQodXJscyRvcGVuRkRBLCB3cml0ZV9kaXNrKHgsIFRSVUUpKSANCiAgICAgICAgdW56aXAoemlwZmlsZSA9IHgpDQogICAgICB9DQogICAgICANCiAgICAgIHJlYWRfanNvbihwYXRoID0ganNvbi5maWxlKTsNCiAgICB9KSgibmRjX2pzb24uemlwIikgfD4gKFwoeCl7DQogICAgICBkYXRhLnRhYmxlOjpyYmluZGxpc3QobW9kaWZ5X2F0KHgkcmVzdWx0cywgMTpsZW5ndGgoeCRyZXN1bHRzKSwgYXMuZGF0YS50YWJsZSksIGZpbGwgPSBUUlVFKSB8Pg0KICAgICAgICBzZXRhdHRyKCJtZXRhZGF0YSIsIHgkbWV0YSkNCiAgICB9KSgpDQogIH0NCn0gZWxzZSB7IA0KICBpZiAoISJvcGVuRkRBX25kYyIgJWluJSBscygpKXsgDQogICAgZGlyKHBhcmFtcyRkYXRhLCBwYXR0ZXJuID0gIm9wZW5GREFfbmRjIiwgZnVsbC5uYW1lcyA9IFRSVUUpIHw+IGxvYWQoKSANCiAgfQ0KfQ0KYGBgDQoNCiMjIyBQcmVwYXJlIERhdGENCg0KTkRDIHNlcXVlbmNlcyBjb21lIGluIGEgdmFyaW91cyBmb3JtYXRzLCB1c3VhbGx5IGEgYDQtNC14YCwgYDUtNC14YCwgb3IgYDUtMy14YCBzZXF1ZW5jZSAoZWFjaCBpbnRlZ2VyIGluZGljYXRpbmcgc3RyaW5nIGxlbmd0aCkuICBTb21ldGltZXMgb3RoZXIgZm9ybWF0cyBhcmlzZSwgc28gbm9ybWFsaXppbmcgYWxsIE5EQyBzZXF1ZW5jZXMgaXMgYSBnb29kIGlkZWEsIGVzcGVjaWFsbHkgd2hlbiB0aGVyZSBpcyBhIGRlc2lyZSAob3IgbmVlZCkgdG8gam9pbiBkaWZmZXJlbnQgZGF0YSBjb250YWluaW5nIGludGVyc2VjdGluZyBORENzLg0KDQpUaGUgZm9sbG93aW5nIHNob3dzIHRoZSBOREMgc2VxdWVuY2VzIGluIHRoZSAqT3BlbkZEQSogYW5kICpNRFJQKiBkYXRhOg0KDQpgYGB7ciBFWFBMT1JBVElPTiwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCmFwaV9kYXRhW3J1bmlmKG49IG5yb3coYXBpX2RhdGEpKSA8PSAwLjMzLCAhImlkIl0gfD4NCiAgZGZTdW1tYXJ5KGxhYmVscy5jb2wgPSBUUlVFKSB8PiANCiAgdmlldyhtZXRob2QgPSAicmVuZGVyIiwgcmVwb3J0LnRpdGxlID0gIk1lZGljYWlkIERydWcgUmViYXRlIFByb2dyYW0gKE1EUlApIERhdGEiKSANCg0KDQpsaXN0KA0KICBgT3BlbkZEQTogTkRDIEZvcm1hdHNgID0gb3BlbkZEQV9uZGNbLCB1bmlxdWUocHJvZHVjdF9uZGMpXSB8PiANCiAgICAgIHNvcnQoKSB8PiANCiAgICAgIHN0cmlfc3BsaXRfZml4ZWQoIi0iKSB8PiANCiAgICAgIG1hcF9jaHIoXCh4KSBzdHJpX2xlbmd0aCh4KSB8PiBwYXN0ZShjb2xsYXBzZSA9ICItIikpIHw+IA0KICAgICAgdGFibGUoKSB8Pg0KICAgICAgIyByYXRpbyh0eXBlID0gIm9mLnN1bSIsIGRlY2ltYWxzID0gNikgfD4NCiAgICAgIGFzLmRhdGEudGFibGUoKQ0KICAsIGBNRFJQOiBOREMgRm9ybWF0c2AgPSBhcGlfZGF0YVssIHBhc3RlKHN0cmlfbGVuZ3RoKGxhYmVsZXJfY29kZSksIHN0cmlfbGVuZ3RoKHByb2R1Y3RfY29kZSksIHNlcCA9ICItIildIHw+DQogICAgICBzb3J0KCkgfD4gDQogICAgICB0YWJsZSgpIHw+DQogICAgICAjIHJhdGlvKHR5cGUgPSAib2Yuc3VtIiwgZGVjaW1hbHMgPSA2KXw+DQogICAgICBhcy5kYXRhLnRhYmxlKCkNCiAgKSB8PiANCiAgaW1hcChcKHgsIHkpeyANCiAgICBwbG90X2x5KCAgDQogICAgICBkYXRhID0geA0KICAgICAgLCB0eXBlID0gInBpZSINCiAgICAgICwgbGFiZWxzID0gfnNwcmludGYoIlslc10iLCBWMSkNCiAgICAgICwgdmFsdWVzID0gfk4NCiAgICAgICwgaG9sZSA9IDAuNg0KICAgICAgLCBuYW1lID0gc3ByaW50ZignTkRDIEZvcm1hdCBGcmVxdWVuY3k6ICVzJywgeSkNCiAgICAgICwgdGV4dGluZm89J2xhYmVsK3BlcmNlbnQnDQogICAgICAsIGluc2lkZXRleHRvcmllbnRhdGlvbj0ncmFkaWFsJw0KICAgICAgKQ0KICB9KSB8Pg0KICBzdWJwbG90KCkgfD4NCiAgICAgIHBsb3RseTo6bGF5b3V0KA0KICAgICAgICB4YXhpcyA9IGxpc3Qoc2hvd2dyaWQgPSBGQUxTRSwgemVyb2xpbmUgPSBGQUxTRSwgc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSkNCiAgICAgICAgLCB5YXhpcyA9IGxpc3Qoc2hvd2dyaWQgPSBGQUxTRSwgemVyb2xpbmUgPSBGQUxTRSwgc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSkNCiAgICAgICAgKSB8Pg0KICAjIHRhZ3MkdWwoKSB8Pg0KICB0YWdzJHAoKQ0KYGBgDQoNCkknbGwgY3JlYXRlIGEgZnVuY3Rpb24gdG8gY2hlY2sgTkRDIGZvcm1hdHMgYW5kIGNvbmZvcm0gdGhlbSBpbiBvcmRlciB0byBqb2luIHRoZSB0d28gZGF0YSBzZXRzIHVzaW5nIGEgY29uZm9ybWVkIE5EQyBzZXF1ZW5jZToNCg0KYGBge3J9DQpjaGVja19uZGNfZm9ybWF0IDwtIFwobGMsIHBjKXsgDQogIHBjIDwtIG1vZGlmeV9pZihwYywgXChpKSBzdHJpX2xlbmd0aChpKSA8IDMsIFwoaSkgc3RyaV9wYWRfbGVmdChpLCB3aWR0aCA9IDMsIHBhZCA9ICIwIikpDQogIGxjIDwtIG1vZGlmeV9pZigNCiAgICAgICAgICBsYw0KICAgICAgICAgICwgXChpKSBzdHJpX2xlbmd0aChpKSA8IDQNCiAgICAgICAgICAsIFwoaSkgc3RyaV9wYWRfbGVmdChpLCB3aWR0aCA9IGlmZWxzZShzdHJpX2xlbmd0aChwYykgPT0gMywgNSwgNCksIHBhZCA9ICIwIikNCiAgICAgICAgICApDQogIHBhc3RlKGxjLCBwYywgc2VwID0gIi0iKQ0KfQ0KYGBgDQoNCg0KYGBge3IgSk9JTl9EUlVHX0RBVEEsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQppZiAocmxhbmc6OmlzX2VtcHR5KGRpcihwYXJhbXMkZGF0YSwgcGF0dGVybiA9ICJtYXN0ZXJfZHJ1Z19kYXRhWy5dW1JyXWRhdGEkIikpKXsNCiAgaWYgKCEibWFzdGVyX2RydWdfZGF0YSIgJWluJSBscygpKXsNCiAgICBtYXN0ZXJfZHJ1Z19kYXRhIDwtIChcKHgsIGkpIHhbDQogICAgICAgICAgICAgICAgICAgIGksIG9uID0gImFsdF9uZGM9PXByb2R1Y3RfbmRjIg0KICAgICAgICAgICAgICAgICAgICAsIG5vbWF0Y2ggPSAwDQogICAgICAgICAgICAgICAgICAgICwgYWxsb3cuY2FydGVzaWFuID0gVFJVRQ0KICAgICAgICAgICAgICAgICAgICAsIGA6PWAocGhhcm1fY2xhc3MgPSBwaGFybV9jbGFzcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBkZWFfc2NoZWR1bGUgPSBkZWFfc2NoZWR1bGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICwgcHJvZHVjdF90eXBlID0gcHJvZHVjdF90eXBlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAsIHJvdXRlID0gcm91dGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICwgbWFya2V0aW5nX2NhdGVnb3J5ID0gbWFya2V0aW5nX2NhdGVnb3J5DQogICAgICAgICAgICAgICAgICAgICAgICAgICApDQogICAgICAgICAgICAgICAgICAgICwgYnkgPSAuRUFDSEkNCiAgICAgICAgICAgICAgICAgICAgXSkoYXBpX2RhdGFbLCBhbHRfbmRjIDo9IG1hcDJfY2hyKGxhYmVsZXJfY29kZSwgcHJvZHVjdF9jb2RlLCBjaGVja19uZGNfZm9ybWF0KV0NCiAgICAgICAgICAgICAgICAgICAgICAsIG9wZW5GREFfbmRjKQ0KICANCiAgICBtYXN0ZXJfZHJ1Z19kYXRhWywgYDo9YCgNCiAgICAgIHBoYXJtX2NsYXNzIDo9IG1hcF9jaHIocGhhcm1fY2xhc3MsIFwoeCkgdW5saXN0KHgpICV8fCUgIn4iKQ0KICAgICAgLCByb3V0ZSA6PSBtYXBfY2hyKHJvdXRlLCBcKHgpIHVubGlzdCh4KSAlfHwlICJ+IikNCiAgICAgICldDQogIH0NCn0gZWxzZSB7DQogIGlmICghIm1hc3Rlcl9kcnVnX2RhdGEiICVpbiUgbHMoKSl7IA0KICAgIGRpcihwYXJhbXMkZGF0YSwgcGF0dGVybiA9ICJtYXN0ZXJfZHJ1Z19kYXRhWy5dW1JyXWRhdGEkIiwgZnVsbC5uYW1lcyA9IFRSVUUpIHw+IGxvYWQoKQ0KICB9DQp9DQpgYGANCg0KDQojIyBUZW1wb3JhbDxicj5BbmFseXNpcyANCg0KYG1hc3Rlcl9kcnVnX2RhdGFgIGlzIGEgZ3JlYXQgZGF0YXNldCBmb3IgY29uc3RydWN0aW5nIHNpbXBsZSwgdGltZS1iYXNlZCBtZXRyaWNzLiAgR2l2ZW4gdGhlIG5hdHVyYWwgb3JkZXIgb2YgdGhlIHR5cGVzIG9mIGV2ZW50cywgaXQgaXMgZWFzeSB0byBzZXR1cCAgZXZlbnQgc2VxdWVuY2UgbWV0cmljcyB1c2luZyBwYWNrYWdlIFtgbHVicmlkYXRlYF0oaHR0cHM6Ly9yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvbHVicmlkYXRlL3ZlcnNpb25zLzEuOS4yKQ0KDQpgYGB7ciBUSU1FX1NUVURZLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KaWYgKHJsYW5nOjppc19lbXB0eShkaXIocGF0dGVybiA9ICJuZGNfZXZlbnRzWy5dW1JyXWRhdGEkIikpKXsgDQogIGlmICghIm5kY19ldmVudHMiICVpbiUgbHMoKSl7IA0KICAgIG5kY19ldmVudHMgPC0gZGVmaW5lKA0KICAgICAgbWFzdGVyX2RydWdfZGF0YQ0KICAgICAgLCB+YWx0X25kYyArIGZkYV9wcm9kdWN0X25hbWUgKyBkcnVnX2NhdGVnb3J5ICsgDQogICAgICAgICAgcGhhcm1fY2xhc3MgKyBkZWFfc2NoZWR1bGUgKyBwcm9kdWN0X3R5cGUgKyANCiAgICAgICAgICByb3V0ZSArIG1hcmtldGluZ19jYXRlZ29yeSArIGZkYV9hcHByb3ZhbF9kYXRlICsgDQogICAgICAgICAgbWFya2V0X2RhdGUgKyB0ZXJtaW5hdGlvbl9kYXRlICsgcmVhY3RpdmF0aW9uX2RhdGUNCiAgICAgICwgdW5pcXVlKC5TRCkNCiAgICAgICwgZGF5c190b19tYXJrZXQgPSBkdXJhdGlvbihhcy5pbnRlZ2VyKG1hcmtldF9kYXRlKSAtIGFzLmludGVnZXIoZmRhX2FwcHJvdmFsX2RhdGUpLCB1bml0cyA9ICJkYXlzIikNCiAgICAgICwgb25fbWFya2V0X2FnZSA9IGR1cmF0aW9uKHsgDQogICAgICAgICAgICBpZmVsc2UoaXMubmEodGVybWluYXRpb25fZGF0ZSksIGlmZWxzZShpcy5uYShyZWFjdGl2YXRpb25fZGF0ZSksIHRvZGF5KCksIHJlYWN0aXZhdGlvbl9kYXRlKSwgdGVybWluYXRpb25fZGF0ZSkgLSANCiAgICAgICAgICAgIGlmZWxzZShpcy5uYShyZWFjdGl2YXRpb25fZGF0ZSksIGlmZWxzZShpcy5uYSh0ZXJtaW5hdGlvbl9kYXRlKSwgbWFya2V0X2RhdGUsIHRlcm1pbmF0aW9uX2RhdGUpLCByZWFjdGl2YXRpb25fZGF0ZSkNCiAgICAgICAgICB9LCB1bml0cyA9ICJkYXlzIikNCiAgICAgICwgZGF5c19tYXJrZXRfYWJzZW50ID0gZHVyYXRpb24oeyANCiAgICAgICAgICAgIGlmZWxzZShpcy5uYShyZWFjdGl2YXRpb25fZGF0ZSksIHRvZGF5KCksIHJlYWN0aXZhdGlvbl9kYXRlKSAtIA0KICAgICAgICAgICAgaWZlbHNlKGlzLm5hKHRlcm1pbmF0aW9uX2RhdGUpLCB0b2RheSgpLCB0ZXJtaW5hdGlvbl9kYXRlKQ0KICAgICAgICAgIH0sIHVuaXRzID0gImRheXMiKQ0KICAgICAgKTsNCiAgICANCiAgICBybGFuZzo6c2V0X25hbWVzKA0KICAgICAgYygiRGF5cyBiZXR3ZWVuIGFwcHJvdmFsIFxuYW5kIG1hcmtldCByZWxlYXNlIg0KICAgICAgICAgICwgIkRheXMgYWN0aXZlIG9uIG1hcmtldCINCiAgICAgICAgICAsICJEYXlzIG1vc3QtcmVjZW50bHkgXG5hYnNlbnQgZnJvbSBtYXJrZXQiKQ0KICAgICAgLCBjKCJkYXlzX3RvX21hcmtldCIsICJvbl9tYXJrZXRfYWdlIiwgImRheXNfbWFya2V0X2Fic2VudCIpDQogICAgICApfD4gDQogICAgICBpd2FsayhcKHgsIHkpIHJsYW5nOjpwYXJzZV9leHByKHNwcmludGYoInNldGF0dHIobmRjX2V2ZW50cyQlcywgXCJsYWJlbFwiLCBcIiVzXCIpIiwgeSwgeCkpIHw+IGV2YWwoZW52aXIgPSAuR2xvYmFsRW52KSkNCiAgfQ0KfSBlbHNlIHsNCiAgaWYgKCEibmRjX2V2ZW50cyIgJWluJSBscygpKXsgDQogICAgZGlyKHBhdHRlcm4gPSAibmRjX2V2ZW50c1suXVtScl1kYXRhJCIsIGZ1bGwubmFtZXMgPSBUUlVFKSB8PiBsb2FkKCkNCiAgfQ0KfQ0KYGBgDQoNCg0KYGBge3IgU0FWRV9EQVRBfQ0KICBib29rLm9mLndvcmtmbG93OjpzYXZlX2ltYWdlKCJvcGVuRkRBX25kYyIsIGZpbGUubmFtZSA9ICJvcGVuRkRBX25kYyINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIHVzZS5wcmVmaXggPSBGQUxTRSwgdXNlLnRpbWVzdGFtcCA9IEZBTFNFLCBzYWZlID0gRkFMU0UpDQogIA0KICBib29rLm9mLndvcmtmbG93OjpzYXZlX2ltYWdlKCJhcGlfZGF0YSIsICJhcGlfZGljdGlvbmFyeSIsIGZpbGUubmFtZSA9ICJtZHJwX2FwaV9kYXRhIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgdXNlLnByZWZpeCA9IEZBTFNFLCB1c2UudGltZXN0YW1wID0gRkFMU0UsIHNhZmUgPSBGQUxTRSkNCiAgDQogIGJvb2sub2Yud29ya2Zsb3c6OnNhdmVfaW1hZ2UoIm1hc3Rlcl9kcnVnX2RhdGEiLCBmaWxlLm5hbWUgPSAibWFzdGVyX2RydWdfZGF0YSINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIHVzZS5wcmVmaXggPSBGQUxTRSwgdXNlLnRpbWVzdGFtcCA9IEZBTFNFLCBzYWZlID0gRkFMU0UpDQogIA0KICBib29rLm9mLndvcmtmbG93OjpzYXZlX2ltYWdlKCJuZGNfZXZlbnRzIiwgZmlsZS5uYW1lID0gIm5kY19ldmVudHMiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCB1c2UucHJlZml4ID0gRkFMU0UsIHVzZS50aW1lc3RhbXAgPSBGQUxTRSwgc2FmZSA9IEZBTFNFKQ0KDQpgYGANCg==